package com.orange.links.client.utils;



import java.util.HashSet;
import java.util.Set;

import com.google.gwt.core.client.GWT;
import com.orange.links.client.shapes.Point;
import com.orange.links.client.shapes.Shape;

public class ConnectionUtils {

	public static Segment computeFasterSegment(Shape startShape, Shape endShape){
		Direction[] d = computeDirections(startShape, endShape);

		Point p1 = pointOnBorder(d[0],startShape);
		Point p2 = pointOnBorder(d[1],endShape);

		return new Segment(p1, p2);
	}

	private static Direction[] computeDirections(Shape s1, Shape s2) {
		Direction[] d1 = Direction.getAll();
		Direction[] d2 = Direction.getAll();

		Direction[] bestDirections = new Direction[2];
		double best = Double.MAX_VALUE;
		for (int i = 0; i < d1.length; i++) {
			for (int j = 0; j < d2.length; j++) {
				double actual = pointOnBorder(d1[i],s1).distance(pointOnBorder(d2[j],s2));
				if( actual < best ){
					best = actual;
					bestDirections[0] = d1[i];
					bestDirections[1] = d2[j];
				}
			}
		}
		return bestDirections;
	}

	public static Point middle(Point p1, Point p2){
		return new Point((p1.getLeft()+p2.getLeft())/2,(p1.getTop()+p2.getTop())/2);
	}

	public static boolean isLinkDiagonal(Rectangle r1, Rectangle r2){

		if(    (r1.getCornerTopLeft().getLeft() > r2.getCornerBottomRight().getLeft()) && (r1.getCornerTopLeft().getTop() > r2.getCornerBottomRight().getTop())
				|| (r1.getCornerTopRight().getLeft() < r2.getCornerBottomLeft().getLeft()) && (r1.getCornerTopRight().getTop() > r2.getCornerBottomLeft().getTop())
				|| (r1.getCornerBottomRight().getLeft() < r2.getCornerTopLeft().getLeft()) && (r1.getCornerBottomRight().getTop() < r2.getCornerTopLeft().getTop())
				|| (r1.getCornerBottomLeft().getLeft() > r2.getCornerTopRight().getLeft()) && (r1.getCornerBottomLeft().getTop() < r2.getCornerTopRight().getTop())
		)
		{
			// Link is Diagonal
			return true;
		}

		return false;
	}

	public static Segment computeSegment(Shape s1, Shape s2) {
		return computeSegment(new Rectangle(s1), new Rectangle(s2));
	}
	

	public static Segment computeSegment(Rectangle r1, Rectangle r2) {

		Point p1 = null;
		Point p2 = null;

		if(isLinkDiagonal(r1, r2)){
			if((r1.getCornerTopLeft().getLeft() >= r2.getCornerBottomRight().getLeft()) && (r1.getCornerTopLeft().getTop() >= r2.getCornerBottomRight().getTop())){
				p1 = r1.getCornerTopLeft();
				p2 = r2.getCornerBottomRight();
			}
			else if ((r1.getCornerTopRight().getLeft() <= r2.getCornerBottomLeft().getLeft()) && (r1.getCornerTopRight().getTop() >= r2.getCornerBottomLeft().getTop())){
				p1 = r1.getCornerTopRight();
				p2 = r2.getCornerBottomLeft();
			}
			else if ((r1.getCornerBottomRight().getLeft() <= r2.getCornerTopLeft().getLeft()) && (r1.getCornerBottomRight().getTop() <= r2.getCornerTopLeft().getTop())){
				p1 = r1.getCornerBottomRight();
				p2 = r2.getCornerTopLeft();
			}
			else if ((r1.getCornerBottomLeft().getLeft() >= r2.getCornerTopRight().getLeft()) && (r1.getCornerBottomLeft().getTop() <= r2.getCornerTopRight().getTop())){
				p1 = r1.getCornerBottomLeft();
				p2 = r2.getCornerTopRight();
			}
			return new Segment(p1, p2);
		}

		// Find the direction (Vertical or Horizontal)
		if(r1.getCornerBottomLeft().getTop() < r2.getCornerTopLeft().getTop()){
			// DOWN
			if(r1.getBorderBottom().length() < r2.getBorderTop().length()){
				// R1 is smaller than R2
				Segment tempSegment = project(r1.getBorderBottom(), r2.getBorderTop());
				if(tempSegment == null){ 
					GWT.log("gwt-debug : The projection has been aborted");
				}
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p1 = new Point(middlePoint.getLeft(),r1.getCornerBottomRight().getTop()-1);
				p2 = new Point(p1.getLeft(),r2.getCornerTopLeft().getTop());
			}else{
				Segment tempSegment = project(r2.getBorderTop(), r1.getBorderBottom());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p2 = new Point(middlePoint.getLeft(),r2.getCornerTopRight().getTop()-1);
				p1 = new Point(p2.getLeft(),r1.getCornerBottomLeft().getTop());
			}
			return new Segment(p1,p2);
		}
		else if(r1.getCornerTopLeft().getTop() > r2.getCornerBottomLeft().getTop()){
			// UP
			if(r1.getBorderTop().length() < r2.getBorderBottom().length()){
				// R1 is smaller than R2
				Segment tempSegment = project(r1.getBorderTop(), r2.getBorderBottom());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p1 = new Point(middlePoint.getLeft(),r1.getCornerTopRight().getTop());
				p2 = new Point(p1.getLeft(),r2.getCornerBottomLeft().getTop());
			}else{
				Segment tempSegment = project( r2.getBorderBottom(), r1.getBorderTop());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p2 = new Point(middlePoint.getLeft(),r2.getCornerBottomRight().getTop());
				p1 = new Point(p2.getLeft(),r1.getCornerTopLeft().getTop());
			}
			return new Segment(p1,p2);
		}
		else if(r1.getCornerTopRight().getLeft() > r2.getCornerTopLeft().getLeft()){
			// LEFT
			if(r1.getBorderRight().length() < r2.getBorderLeft().length()){
				// R1 is smaller than R2
				Segment tempSegment = project(r1.getBorderLeft(), r2.getBorderRight());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p1 = new Point(r1.getCornerTopLeft().getLeft(),middlePoint.getTop());
				p2 = new Point(r2.getCornerBottomRight().getLeft(),p1.getTop());
			}else{
				Segment tempSegment = project(r2.getBorderRight(), r1.getBorderLeft());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p2 = new Point(r2.getCornerTopRight().getLeft(),middlePoint.getTop());
				p1 = new Point(r1.getCornerBottomLeft().getLeft(),p2.getTop());
			}
			return new Segment(p1,p2);
		}
		else if(r1.getCornerTopLeft().getLeft() < r2.getCornerTopRight().getLeft()){
			// RIGHT
			if(r1.getBorderRight().length() < r2.getBorderLeft().length()){
				// R1 is smaller than R2
				Segment tempSegment = project(r1.getBorderRight(), r2.getBorderLeft());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p1 = new Point(r1.getCornerBottomRight().getLeft(),middlePoint.getTop());
				p2 = new Point(r2.getCornerBottomLeft().getLeft(),p1.getTop());
			}else{
				Segment tempSegment = project(r2.getBorderRight(), r1.getBorderLeft());
				Point middlePoint = middle(tempSegment.getP1(),tempSegment.getP2());
				p2 = new Point(r2.getCornerTopLeft().getLeft(),middlePoint.getTop());
				p1 = new Point(r1.getCornerBottomRight().getLeft(),p2.getTop());
			}
			return new Segment(p1,p2);
		} 

		return null;
	}

	/**
	 * Use to compute the projection of s1 on s2.
	 * S1 and s2 must be vertical or horizontal
	 * 
	 * @return the projected segment on s2
	 */
	public static Segment project(Segment s1, Segment s2){
		if(s1.getP1().getLeft() == s1.getP2().getLeft() && s2.getP1().getLeft() == s2.getP2().getLeft()){
			// Lines vertical
			Point p1;
			Point p2;
			if(s1.getP2().getTop() >= s2.getP2().getTop()){
				// S1 on the bottom of S2
				if(s1.getP1().getTop() > s2.getP2().getTop()){
					// No intersection
					return null;
				}
				p1 = new Point(s2.getP1().getLeft(),s1.getP1().getTop());
				p2 = new Point(s2.getP1().getLeft(),s2.getP2().getTop());
				return new Segment(p1,p2);
			}
			else if(s1.getP1().getTop() <= s2.getP1().getTop()){
				// S1 on the Top of S2
				if(s1.getP2().getTop() < s2.getP1().getTop()){
					// No intersection
					return null;
				}
				p1 = new Point(s2.getP1().getLeft(),s2.getP1().getTop());
				p2 = new Point(s2.getP1().getLeft(),s1.getP2().getTop());
				return new Segment(p1,p2);
			}
			else{
				// S1 inside S2
				p1 = new Point(s2.getP1().getLeft(),s1.getP1().getTop());
				p2 = new Point(s2.getP1().getLeft(),s1.getP2().getTop());
				return new Segment(p1,p2);
			}
		}
		else if (s1.getP1().getTop() == s1.getP2().getTop() && s2.getP1().getTop() == s2.getP2().getTop()) {
			// Lines horizontal
			Point p1;
			Point p2;
			if(s1.getP1().getLeft() <= s2.getP1().getLeft()){
				// S1 on the left of S2
				if(s1.getP2().getLeft() < s2.getP1().getLeft()){
					// No intersection
					return null;
				}
				p1 = new Point(s2.getP1().getLeft(),s2.getP1().getTop());
				p2 = new Point(s1.getP2().getLeft(),s2.getP1().getTop());
				return new Segment(p1,p2);
			}
			else if(s1.getP2().getLeft() >= s2.getP2().getLeft()){
				// S1 on the right of S2
				if(s1.getP1().getLeft() > s2.getP2().getLeft()){
					// No intersection
					return null;
				}
				p1 = new Point(s1.getP1().getLeft(),s2.getP1().getTop());
				p2 = new Point(s2.getP2().getLeft(),s2.getP1().getTop());
				return new Segment(p1,p2);
			}
			else{
				// S1 inside S2
				p1 = new Point(s1.getP1().getLeft(),s2.getP1().getTop());
				p2 = new Point(s1.getP2().getLeft(),s2.getP1().getTop());
				return new Segment(p1,p2);
			}
		}
		else {
			throw new IllegalArgumentException("The segment must be parallel and horizontal or vertical");
		}
	};
	
	public static Set<Point> pointsOnBorder(Shape s){
		Set<Point> pointSet = new HashSet<Point>();
		int offsetLeft = s.getLeft();
		int offsetTop = s.getTop();
		int width = s.getWidth();
		int height = s.getHeight();
		for(int i = offsetLeft; i < offsetLeft+width ; i=i+1){
			Point p1 = new Point(i, offsetTop);
			Point p2 = new Point(i, offsetTop+height);
			pointSet.add(p1);
			pointSet.add(p2);
		}
		for(int i = offsetTop ; i < offsetTop + height ; i=i+1){
			Point p1 = new Point(offsetLeft, i);
			Point p2 = new Point(offsetLeft+width, i);
			pointSet.add(p1);
			pointSet.add(p2);
		}
		return pointSet;
	}

	private static Point pointOnBorder(Direction d, Shape s){
		
		// Compute distance
		int distance;
		if(d.isHorizontal())
			distance = s.getWidth()/2;
		else if(d.isVertical())
			distance = s.getHeight()/2;
		else
			distance = (int) Math.sqrt( Math.pow(s.getWidth(),2) +  Math.pow(s.getHeight(),2)) /2;
			
		Point p = new Point(s.getLeft()+s.getWidth()/2,s.getTop()+s.getHeight()/2).move(d,distance);
		p.setDirection(d);
		return p;
	}

	public static double distanceToSegment(Segment s, Point p) {
		return distanceToSegment(s.getP1(), s.getP2(), p);
	}

	public static double distanceToSegment(Point p1, Point p2, Point p3) {

		final double xDelta = p2.getLeft() - p1.getLeft();
		final double yDelta = p2.getTop() - p1.getTop();

		if ((xDelta == 0) && (yDelta == 0)) {
			throw new IllegalArgumentException("p1 and p2 cannot be the same point");
		}

		final double u = ((p3.getLeft() - p1.getLeft()) * xDelta + (p3.getTop() - p1.getTop()) * yDelta) / (xDelta * xDelta + yDelta * yDelta);

		final Point closestPoint;
		if (u < 0) {
			closestPoint = p1;
		} else if (u > 1) {
			closestPoint = p2;
		} else {
			closestPoint = new Point((Integer)p1.getLeft() + u * xDelta,(Integer) p1.getTop() + u * yDelta);
		}

		return closestPoint.distance(p3);
	}
	
	public static Point projectionOnSegment(Segment s, Point p) {
		return projectionOnSegment(s.getP1(), s.getP2(), p);
	}

	public static Point projectionOnSegment(Point p1, Point p2, Point p3) {

		final double xDelta = p2.getLeft() - p1.getLeft();
		final double yDelta = p2.getTop() - p1.getTop();

		if ((xDelta == 0) && (yDelta == 0)) {
			throw new IllegalArgumentException("p1 and p2 cannot be the same point");
		}

		final double u = ((p3.getLeft() - p1.getLeft()) * xDelta + (p3.getTop() - p1.getTop()) * yDelta) / (xDelta * xDelta + yDelta * yDelta);

		final Point closestPoint;
		if (u < 0) {
			closestPoint = p1;
		} else if (u > 1) {
			closestPoint = p2;
		} else {
			closestPoint = new Point((Integer)p1.getLeft() + u * xDelta,(Integer) p1.getTop() + u * yDelta);
		}

		return closestPoint;
	}

}
